第7章 迭代器与生成器

循环是迭代机制的基础,但是通过循环来进行迭代有两个限制:

因此提出了迭代器模式,即实现了 Iterable 接口的对象称为可迭代对象。可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。

实现 Iterable 接口要求同时具备两种能力:

  1. 支持迭代的自我识别能力
  2. 创建实现 Iterator 接口的对象的能力

这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的 Symbol.iterator 作为键。这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。检查是否存在默认迭代器属性可以暴露这个工厂函数:

let num = 1;
let obj = {};
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined

let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]);
console.log(arr[Symbol.iterator]);
console.log(map[Symbol.iterator]);
console.log(set[Symbol.iterator]);
console.log(els[Symbol.iterator]);
// ƒ [Symbol.iterator]() { [native code] }
// ƒ values() { [native code] }
// ƒ entries() { [native code] }
// ƒ values() { [native code] }
// ƒ values() { [native code] }

// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {} 
console.log(arr[Symbol.iterator]()); // Array Iterator {} 
console.log(map[Symbol.iterator]()); // MapIterator {'a' => 1, 'b' => 2, 'c' => 3}
console.log(set[Symbol.iterator]()); // SetIterator {'a', 'b', 'c'}
console.log(els[Symbol.iterator]()); // Array Iterator {}

迭代器使用 next() 方法在可迭代对象中遍历数据。每次成功调用 next(),都会返回一个 IteratorResult 对象,其中包含迭代器返回的下一个值。若不调用 next(),则无法知道迭代器的当前位置。

IteratorResult 对象包含两个属性:done 和 value。done 是一个布尔值,表示是否还可以再次调用 next() 取得下一个值;value 包含可迭代对象的下一个值或者 undefined 。

// 可迭代对象
let arr = ['foo', 'bar'];
// 获取迭代器
let iter = arr[Symbol.iterator]();
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }

如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化:

let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
// 在数组中间插入值 
arr.splice(1, 0, 'bar');
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: false, value: 'baz' }
console.log(iter.next()); // { done: true, value: undefined }

自定义迭代器

与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用

class Counter {
    // Counter 的实例应该迭代 limit 次 
  constructor(limit) {
    this.limit = limit;
  }

  [Symbol.iterator]() {
    let count = 1, limit = this.limit;
    return {
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true, value: undefined };
        }
      }
    };
  }
}

let counter = new Counter(3); 
for (let i of counter) {
  console.log(i);
}
// 1
// 2
// 3

对象迭代过程中,可以通过 return() 方法提前终止迭代器。return() 方法必须返回一个有效的 IteratorResult 对象,例如只返回 { done: true }。

class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  [Symbol.iterator]() {
    let count = 1, limit = this.limit;
    return {
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
      return() {
        console.log('Exiting early');
        return { done: true };
      }
    };
  }
}

let counter1 = new Counter(5);
for (let i of counter1) {
    if (i > 2) {
    break; 
  }
  console.log(i);
}
// 1
// 2
// Exiting early

let counter2 = new Counter(5);
try {
  for (let i of counter2) {
    if (i > 2) {
      throw 'err';
    }
    console.log(i);
  }
} catch(e) {}
// 1
// 2
// Exiting early

let counter3 = new Counter(5);
let [a, b] = counter3;
// Exiting early

因为 return() 方法是可选的,所以并非所有迭代器都是可关闭的。想知道某个迭代器是否可关闭,可以测试这个迭代器实例的 return 属性是不是函数对象。

生成器

生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器,注意箭头函数不能用来定义生成器函数

// 生成器函数声明
function* generatorFn() {}

// 生成器函数表达式
let generatorFn = function* () {}

// 作为对象字面量方法的生成器函数 
let foo = {
  * generatorFn() {}
}

// 作为类实例方法的生成器函数 
class Foo {
  * generatorFn() {}
}

// 作为类静态方法的生成器函数 
class Bar {
  static * generatorFn() {}
}

调用生成器函数会产生一个生成器对象,生成器对象一开始处于暂停执行状态。与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next() 方法,调用这个方法会让生成器开始或恢复执行

function* generatorFn() {}
const g = generatorFn();
console.log(g);       // generatorFn {<suspended>}
console.log(g.next);  // f next() { [native code] }

next() 方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。函数体为空的生成器函数中间不会停留,调用一次 next()就会让生成器到达 done: true 状态

function* generatorFn() {}
let gen = generatorFn();
console.log(gen); // generatorFn {<suspended>}
console.log(gen.next()); // { done: true, value: undefined }
// value 属性是生成器函数的返回值,默认值为 undefined
// 可以通过生成器函数的返回值指定

生成器函数只会在初次调用 next() 方法后开始执行,生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的

function* generatorFn() {
  console.log('foobar');
}
// 初次调用生成器函数并不会打印日志
let generatorObject = generatorFn();
generatorObject.next(); // foobar

console.log(generatorFn);
// f* generatorFn() {}
console.log(generatorFn()[Symbol.iterator]);
// f [Symbol.iterator]() {native code}
console.log(generatorFn());
// generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]());
// generatorFn {<suspended>}
const g = generatorFn();
console.log(g === g[Symbol.iterator]());
// true

正常运行中的生成器遇到 yield 关键字时会停止运行,函数作用域状态也会被保留,之后可以通过调用 next() 方法来恢复执行

function* generatorFn() {
  yield;
}
let gen = generatorFn();
console.log(gen.next()); // { done: false, value: undefined }
console.log(gen.next()); // { done: true, value: undefined }

这里需要区分一下 yield 关键字和 return 关键字,通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态

function* generatorFn() {
    yield 'foo';
  yield 'bar';
  return 'foobar';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'foobar' }

与迭代器类似,生成器也支持“可关闭”的概念,通过可选的 return() 方法或者 throw() 方法提前终止迭代器

function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>} 
console.log(g.next); // f next() { [native code] } 
console.log(g.return); // f return() { [native code] } 
console.log(g.throw); // f throw() { [native code] }

return() 方法会强制生成器进入关闭状态,传给 return() 方法的值,就是终止迭代器对象的值

function* generatorFn() {
  for (const x of [1, 2, 3]) {
    yield x; 
  }
}

const g = generatorFn();
console.log(g);            // generatorFn {<suspended>}
console.log(g.next());       // { done: false, value: 1 }
console.log(g.return(4));  // { done: true, value: 4 }
// 通过 return() 进入关闭状态,就无法恢复了
console.log(g.next());       // { done: true, value: undefined }
console.log(g);            // generatorFn {<closed>}

throw() 方法会在暂停的时候将一个提供的错误注入到生成器对象中,如果错误未被处理,生成器就会关闭

function* generatorFn() {
  for (const x of [1, 2, 3]) {
    yield x; 
  }
}

const g = generatorFn();
console.log(g);   // generatorFn {<suspended>}
try {
  g.throw('foo');
} catch (e) {
  console.log(e); // foo
}
console.log(g);   // generatorFn {<closed>}

假如生成器函数内部处理了这个错误,那么生成器就不会关闭,还可以恢复执行,错误处理会跳过对应的 yield

function* generatorFn() {
  for (const x of [1, 2, 3]) {
    try { 
      yield x;
    } catch(e) {}
  } 
}

const g = generatorFn();
console.log(g.next()); // { done: false, value: 1} 
g.throw('foo'); // 主动抛出了一个错误
console.log(g.next()); // { done: false, value: 3}